package com.matrix.common.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.matrix.common.constant.HeaderConstants;
import com.matrix.common.domain.Result;
import com.matrix.common.exception.BusinessException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Objects;

/**
 * @author 有点甜
 * @since 2025/8/6
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class IdempotencyAspect {

    private final StringRedisTemplate stringRedisTemplate;
    private final ObjectMapper objectMapper;

    private static final String REDIS_KEY_PREFIX = "idempotent:";
    private static final String RESULT_SUFFIX = ":result";

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        String token = request.getHeader(HeaderConstants.IDEMPOTENCY_TOKEN);

        if (!StringUtils.hasText(token)) {
            throw new BusinessException("缺少幂等性校验令牌 (X-Request-ID)");
        }

        String key = REDIS_KEY_PREFIX + token;
        String resultKey = key + RESULT_SUFFIX;

        Boolean isFirstRequest = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", idempotent.expireTime(), idempotent.timeUnit());

        if (Boolean.FALSE.equals(isFirstRequest)) {
            log.warn("检测到重复请求, token: {}", token);
            String cachedResultJson = stringRedisTemplate.opsForValue().get(resultKey);
            if (StringUtils.hasText(cachedResultJson)) {
                log.info("返回缓存的幂等性结果, token: {}", token);
                return objectMapper.readValue(cachedResultJson, Result.class);
            }
            throw new BusinessException(idempotent.message());
        }

        try {
            Object result = joinPoint.proceed();

            if (result instanceof Result && ((Result<?>) result).getCode() == 200) {
                String resultJson = objectMapper.writeValueAsString(result);
                stringRedisTemplate.opsForValue().set(resultKey, resultJson, idempotent.cacheTime(), idempotent.cacheTimeUnit());
            }
            return result;
        } catch (Throwable e) {
            log.error("幂等性保护的方法执行异常, token: {}, 准备释放令牌", token, e);
            stringRedisTemplate.delete(key);
            throw e;
        }
    }
}
